// Save and load data
//--------------------------------
#ifndef SAVE_H
#define SAVE_H
#include "common.h"
#include <boost/unordered_map.hpp>

class Save {
public:

    // How to use the saving functionality in class Foo
    // * Derive Foo from Save::Node
    // * Foo must have a no-argument constructor which leaves it in a state
    //   ready to be loaded - no memory allocation!
    // * Within definition of Foo, include the following macro
    //      NODE_TYPE( Foo ) {
    //          PARENT( Bar );
    //          DATA( _member );
    //          DATA( _anotherMember );
    //          ...
    //      }
    //   Where Bar is the parent class of Foo, if Foo derives indirectly
    //   from Save::Node (omit otherwise)
    // * Call Save::RegisterNodeType< Foo >() at some point before
    //   loading any graph containing Foos
    // * Clean up with Save::UnregisterNodeTypes()

    class Node {
    public:

        Node() { }
        virtual ~Node() { }

        virtual void Data( std::ostream& out ) = 0;
        virtual void Data( std::istream&  in ) = 0;

        virtual std::string GetTypenameVirtual() const = 0;
        #define NODE_TYPE( type )                        \
        virtual std::string GetTypenameVirtual() const   \
        {                                                \
            return #type ;                               \
        }                                                \
        static std::string GetTypenameStatic()           \
        {                                                \
            return #type ;                               \
        }                                                \
        virtual void Data( std::istream& in )            \
        {                                                \
            Data< std::istream >( in );                  \
        }                                                \
        virtual void Data( std::ostream& out )           \
        {                                                \
            Data< std::ostream >( out );                 \
        }                                                \
        template< class Stream >                         \
        void Data( Stream& s )
        #define DATA( t ) Save::Data( s, t )
        #define PARENT( type ) type ::Data( s )

    };

    // Save and load graphs of object nodes
    static void  SaveGraph( std::ostream& out, const Node* startingNode );
    static Node* LoadGraph( std::istream& in );

    template< class N >
    static N* LoadGraph( std::istream& in );

    template< class N >
    static void RegisterNodeType();
    static void UnregisterNodeTypes();

    // Primitives
    #define SAVE_PRIMITIVE( type )                   \
    static void Data( std::ostream& out, type   t ); \
    static void Data( std::istream&  in, type & t )

    SAVE_PRIMITIVE( int );
    SAVE_PRIMITIVE( unsigned int );
    SAVE_PRIMITIVE( short );
    SAVE_PRIMITIVE( unsigned short );
    SAVE_PRIMITIVE( long );
    SAVE_PRIMITIVE( unsigned long );
    SAVE_PRIMITIVE( char );
    SAVE_PRIMITIVE( unsigned char );
    SAVE_PRIMITIVE( signed char );
    SAVE_PRIMITIVE( float );
    SAVE_PRIMITIVE( double );

    static void Data( std::ostream& out, const std::string& t );
    static void Data( std::istream&  in,       std::string& t );

    // Nonprimitives
    static void Data( std::ostream& out, const Node& t );
    static void Data( std::istream&  in,       Node& t );

    static void Data( std::ostream& out, const Node*  t );
    static void Data( std::istream&  in,       Node*& t );

    template< typename T >
    static void Data( std::ostream& out, const std::vector< T >& t );
    template< typename T >
    static void Data( std::istream&  in,       std::vector< T >& t );

    template< typename T, typename S >
    static void Data( std::ostream& out, const std::pair< T, S >& t );
    template< typename T, typename S >
    static void Data( std::istream&  in,       std::pair< T, S >& t );

    // Hack
    template< typename T >
    static void Data( std::istream& in, T*& t );

private:

    // Data structure used when saving a graph
    typedef boost::unordered_map< const Node*, uint > PointerMap;
    typedef std::vector         < const Node* >       NodeQueue;
    struct SaveInfo {
        PointerMap map;
        NodeQueue  queue;
    };
    // Data structure used when loading a graph
    typedef boost::unordered_map< uint, Node* > IdMap;
    typedef std::vector< Node** >               NodeList;
    struct LoadInfo {
        IdMap    map;
        NodeList list;
    };

    // Factory used when loading a graph
    class AbstractFactory {
    public:
        virtual Node* Load() const = 0;
    };
    template< class Node >
    class Factory : public AbstractFactory {
    public:
        virtual Node* Load() const
        {
            return new Node;
        }
    };
    typedef boost::unordered_map< std::string, AbstractFactory* > FactoryMap;
    typedef std::pair           < std::string, AbstractFactory* > FactoryPair;
    static FactoryMap _factory;

    static SaveInfo* _saveInfo;
    static LoadInfo* _loadInfo;

};

template< class N >
void Save::RegisterNodeType()
{
    _factory[ N::GetTypenameStatic() ] = new Factory< N >();
}

template< class N >
N* Save::LoadGraph( std::istream& in )
{
    return dynamic_cast< N* >( LoadGraph( in ) );
}

template< typename T >
void Save::Data( std::ostream& out, const std::vector< T >& t )
{
    out << t.size() << " ";
    foreach ( const T& tt, t ) {
        Data( out, tt );
    }
}

template< typename T >
void Save::Data( std::istream& in, std::vector< T >& t )
{
    uint size;
    in >> size;
    for ( uint i = 0; i < size; i++ ) {
        t.push_back( 0 );
    }
    for ( uint i = 0; i < size; i++ ) {
        Data( in, t[ i ] );
    }
}

template< typename T, typename S >
void Save::Data( std::ostream& out, const std::pair< T, S >& t )
{
    Data( out, t.first );
    Data( out, t.second );
}

template< typename T, typename S >
void Save::Data( std::istream&  in, std::pair< T, S >& t )
{
    Data( in, t.first );
    Data( in, t.second );
}

template< typename T >
void Save::Data( std::istream& in, T*& t )
{
    Data( in, reinterpret_cast< Node*& >( t ) );
}

#endif
